#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utils.h>
#include <printk.h>
#include <fs-poll.h>

#define LOG_NR (2)

#define CONSOLEBUF_SIZE 1024

static char log_buf[CONSOLEBUF_SIZE];

struct log_method
{
    int  (*setup)(ev_loop_t *loop, void *arg);
    void (*setdown)(void);
    int  (*restart)(void *arg);
    void (*log_simple)(u8 m, u8 lvl, const char *fmt, va_list args);
    void (*log_arr_simple)(u8 m, u8 lvl, const void *arr, size_t len);
    void (*log_set_lvl)(u8 m, u8 lvl);
    void (*log_set_fmt)(u8 m, size_t set);
};
struct log_ctl_block
{
    struct object obj;
    struct file_poll log_poll;
};
static struct log_ctl_block lcb;

/* zlog
 */
static zlog_category_t *categorys[LOG_MODULE_NR];
static inline zlog_category_t *sc_zlog_get_category(u32 module)
{
    if (module < array_size(categorys))
        return categorys[module];
    return NULL;
}
static inline u8 to_zlog_lvl(u8 lvl)
{
    static const u8 lvl_tab[LOG_LEVEL_NR] = {
        ZLOG_LEVEL_ERROR,
        ZLOG_LEVEL_INFO,
        ZLOG_LEVEL_DEBUG
    };

    if (lvl >= LOG_LEVEL_NR)
        lvl = LOG_LEVEL_DEF;
    return lvl_tab[lvl];
}
static void zlog_simple(u8 m, u8 lvl, const char *fmt, va_list args)
{
    vzlog(sc_zlog_get_category(m), "", 0, "", 0, 0, to_zlog_lvl(lvl), fmt, args);
}
static void zlog_arr_simple(u8 m, u8 lvl, const void *arr, size_t len)
{
    hzlog(sc_zlog_get_category(m), "", 0, "", 0, 0, to_zlog_lvl(lvl), arr, len);
}
static void zlog_set_lvl(u8 m, u8 lvl)
{
    zlog_category_t *c = sc_zlog_get_category(m);
    if (c)
        zlog_level_switch(c, lvl);
}
static int zlog_set_default(const char *cfgfile, const char *logdir)
{
    static const char *dft =
        "[global]\n"
        "rotate lock file = self\n"
        "default format   = \"[%%d] %%m\"\n"
        "[formats]\n"
        "simple           = \"[%%d] %%m\"\n"
        "[rules]\n"
        "#*.DEBUG         >stdout; simple\n"
        "*.INFO           >stdout; simple\n"
        "#*.!DEBUG        \"%s/ECCE.log\", 2MB*20 ~ \"%s/ECCE.log.#2r\"; simple\n"
        ;
    FILE *fp;

    if ((fp = fopen(cfgfile, "w")) == NULL)
    {
        printk("<log>: crate configure file[%s] fail\n", cfgfile);
        return -1;
    }
    fprintf(fp, dft, logdir, logdir);
    fclose(fp);
    return 0;
}
static void log_poll_cb(struct file_poll *fp)
{
    log_restart((void *)fp->path);
}
static int zlog_setup(ev_loop_t *loop, void *arg)
{
    int i, r;
    int tries = 3;
    const char *cfgfile = arg;

    while (tries-- > 0)
    {
        r = zlog_init(cfgfile);
        if (r == 0)
            break;
        zlog_set_default(cfgfile, "/tmp/log");
    }
    if (r)
    {
        printk("<log>: init failed, cfg path[%s]\n", cfgfile);
        return -1;
    }

    static const char *cat_map[] = {
        [LOG_MODULE_DEV] = "DEV_CAT",
        [LOG_MODULE_CHN] = "CHN_CAT",
        [LOG_MODULE_MDM] = "MDM_CAT",
        [LOG_MODULE_DB]  = "DB_CAT",
        [LOG_MODULE_APP] = "APP_CAT",
    };
    for (i = 0; i < LOG_MODULE_NR; i++)
    {
        categorys[i] = zlog_get_category(cat_map[i]);
        if (!categorys[i])
        {
            printk("<log>: get cat[%s] fail\n", cat_map[i]);
            zlog_fini();
            return -1;
        }
    }

    r = fs_poll_add(&lcb.log_poll, loop, cfgfile, 1*HZ, log_poll_cb);
    assert(r == 0);
    return 0;
}
static void zlog_setdown(void)
{
    zlog_fini();
}
static int zlog_restart(void *arg)
{
    const char *cfgfile = arg;

    return zlog_reload(cfgfile);
}
static const struct log_method log_method_zlog =
{
    .setup          = zlog_setup,
    .setdown        = zlog_setdown,
    .restart        = zlog_restart,
    .log_simple     = zlog_simple,
    .log_arr_simple = zlog_arr_simple,
    .log_set_lvl    = zlog_set_lvl,
};

/* dlog
 */
struct dlog_category
{
    u8          lvl;
    u8          fmt;
    const char *tag;
};
struct dlogger
{
    struct dlog_category cats[LOG_MODULE_NR + 1];
    size_t enabled_fmt_set[LOG_LEVEL_NR];
};
static struct dlogger logger =
{
    .cats =
    {
        [LOG_MODULE_DEV] = {.lvl = LOG_LEVEL_INF, .fmt = EFMT_TM, .tag = "DEV",},
        [LOG_MODULE_CHN] = {.lvl = LOG_LEVEL_INF, .fmt = EFMT_TM, .tag = "CHN",},
        [LOG_MODULE_MDM] = {.lvl = LOG_LEVEL_INF, .fmt = EFMT_TM, .tag = "MDM",},
        [LOG_MODULE_DB]  = {.lvl = LOG_LEVEL_INF, .fmt = EFMT_TM, .tag = "DB",},
        [LOG_MODULE_APP] = {.lvl = LOG_LEVEL_INF, .fmt = EFMT_TM, .tag = "APP",},
    },
};
static char dlog_buf[CONSOLEBUF_SIZE];
static const char *lvl_name[] = {"ERR", "INF", "DBG"};

static void dlog_set_lvl(u8 m, u8 lvl)
{
    if (m < array_size(logger.cats))
        logger.cats[m].lvl = lvl;
}
static void dlog_set_fmt(u8 m, size_t set)
{
    if (m < array_size(logger.cats))
        logger.cats[m].fmt = set;
}
static inline int get_fmt_enabled(struct dlog_category *cat, size_t set)
{
    return cat->fmt & set;
}
extern void get_cur_bcd_time(u8 *bcdtm);
static void dlog_simple(u8 m, u8 lvl, const char *fmt, va_list args)
{
    if (m > LOG_MODULE_NR || lvl > logger.cats[m].lvl) return;

    int n = 0;
    struct dlog_category *cat = &logger.cats[m];

    if (get_fmt_enabled(cat, EFMT_TM))
    {
        dlog_buf[n++] = '[';

        u8 tm[6];
        get_cur_bcd_time(tm);
        n += snprintf(dlog_buf + n, sizeof(dlog_buf) - n, "%02X-%02X-%02X %02X:%02X:%02X",
                      tm[5], tm[4], tm[3], tm[2], tm[1], tm[0]);
#if 0
        u32 ms = hw_time() * 10;
        n += snprintf(dlog_buf + n, sizeof(dlog_buf) - n, " %u.%03u", ms / 1000, ms % 1000);
#endif
        dlog_buf[n++] = ']';
    }

    if (get_fmt_enabled(cat, EFMT_LVL))
    {
        dlog_buf[n++] = '[';
        n += snprintf(dlog_buf + n, sizeof(dlog_buf) - n, "%s", lvl_name[lvl]);
        dlog_buf[n++] = ']';
    }

    if (get_fmt_enabled(cat, EFMT_TAG))
    {
        dlog_buf[n++] = '[';
        n += snprintf(dlog_buf + n, sizeof(dlog_buf) - n, "%s", logger.cats[m].tag);
        dlog_buf[n++] = ']';
    }

    n += vsnprintf(dlog_buf + n, sizeof(dlog_buf) - n, fmt, args);

    fprintf(stdout, "%s", dlog_buf);
    fflush(stdout);
}
static void dlog_arr_simple(u8 module, u8 lvl, const void *arr, size_t len)
{
    if (module > LOG_MODULE_NR || lvl > logger.cats[module].lvl) return;

    int idx = 0;

    while (len)
    {
        int _len = min(len, 64);
        arr2str((uint8_t *)arr + idx, _len, log_buf, sizeof(log_buf));
        fprintf(stdout, "%s ", log_buf);
        len -= _len;
        idx += _len;
    }
    fprintf(stdout, "\n");
    fflush(stdout);
}
static const struct log_method log_method_dlog =
{
    .log_simple     = dlog_simple,
    .log_arr_simple = dlog_arr_simple,
    .log_set_lvl    = dlog_set_lvl,
    .log_set_fmt    = dlog_set_fmt,
};

static const struct log_method *log_cur = &log_method_dlog;

void log_simple(u8 m, u8 lvl, const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    log_cur->log_simple(m, lvl, fmt, args);
    va_end(args);
}
void log_arr_simple(u8 m, u8 lvl, const void *arr, size_t len)
{
    log_cur->log_arr_simple(m, lvl, arr, len);
}
void log_set_fmt(u8 m, size_t set)
{
    if (log_cur->log_set_fmt)
        log_cur->log_set_fmt(m, set);
}
void log_set_lvl(u8 m, u8 lvl)
{
    if (log_cur->log_set_lvl)
        log_cur->log_set_lvl(m, lvl);
}

static const struct log_method *get_method(int method)
{
    switch (method)
    {
    case LOG_METHOD_ZLOG:
        return &log_method_zlog;
    case LOG_METHOD_ELOG:
    default:
        return &log_method_dlog;
    }
}

int log_setup(ev_loop_t *loop, int method, void *arg)
{
    const struct log_method *l = get_method(method);
    int r = 0;

    if (l->setup)
        r = l->setup(loop, arg);

    if (r == 0)
        log_cur = l;
    return r;
}
void log_setdown(void)
{
    if (log_cur->setdown)
        log_cur->setdown();
}
int log_restart(void *arg)
{
    if (log_cur->restart)
        return log_cur->restart(arg);
    return 0;
}

static int (*console_write_func)(const void *buf, int len);

void printk_set_output(int (*func)(const void *, int))
{
    console_write_func = func;
}

void printk(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    int n = vsnprintf(log_buf, sizeof(log_buf), fmt, args);

    if (console_write_func)
    {
        console_write_func(log_buf, n);
    }
    else
    {
        fprintf(stdout, "%s", log_buf);
        fflush(stdout);
    }
    va_end(args);
}

void pr_arr(const void *arr, size_t len)
{
    int idx = 0;

    while (len)
    {
        int _len = min(len, 16);
        arr2str((uint8_t *)arr + idx, _len, log_buf, sizeof(log_buf));
        if (console_write_func)
        {
            console_write_func(log_buf, strlen(log_buf));
            console_write_func("\r\n", 2);
        }
        else
        {
            fprintf(stdout, "%s\n", log_buf);
            fflush(stdout);
        }
        len -= _len;
        idx += _len;
    }
}
